package com.shuaigexue.exam.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shuaigexue.exam.common.CacheConstants;
import com.shuaigexue.exam.entity.PaperQuestion;
import com.shuaigexue.exam.entity.Question;
import com.shuaigexue.exam.entity.QuestionAnswer;
import com.shuaigexue.exam.entity.QuestionChoice;
import com.shuaigexue.exam.mapper.PaperQuestionMapper;
import com.shuaigexue.exam.mapper.QuestionAnswerMapper;
import com.shuaigexue.exam.mapper.QuestionChoiceMapper;
import com.shuaigexue.exam.mapper.QuestionMapper;
import com.shuaigexue.exam.service.QuestionService;
import com.shuaigexue.exam.utils.RedisUtils;
import com.shuaigexue.exam.vo.QuestionQueryVo;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 题目Service实现类
 * 实现题目相关的业务逻辑
 */
@Slf4j
@Service
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question> implements QuestionService {


    @Resource
    private QuestionMapper questionMapper;

    @Resource
    private QuestionChoiceMapper questionChoiceMapper;

    @Resource
    private QuestionAnswerMapper questionAnswerMapper;

    @Resource
    private RedisUtils redisUtils;

    @Resource
    private PaperQuestionMapper paperQuestionMapper;

    /**
     * 分页查询题目信息：方案2 进行分布查询 1 + n
     * @param questionPage
     * @param questionQueryVo
     */
    @Override
    public void queryQuestionListByPage(Page<Question> questionPage, QuestionQueryVo questionQueryVo) {
        questionMapper.selectQuestionPage(questionPage, questionQueryVo);
    }

    /**
     * 分页查询题目信息：方案3 java代码进行处理
     * @param questionPage
     * @param questionQueryVo
     */
    @Override
    public void queryQuestionListByStream(Page<Question> questionPage, QuestionQueryVo questionQueryVo) {
        // 1. 题目单表的分页 + 动态条件查询(mybatis-plus)
        LambdaQueryWrapper<Question> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(questionQueryVo.getCategoryId() != null, Question::getCategoryId, questionQueryVo.getCategoryId());
        wrapper.eq(!ObjectUtils.isEmpty(questionQueryVo.getDifficulty()), Question::getDifficulty, questionQueryVo.getDifficulty());
        wrapper.eq(!ObjectUtils.isEmpty(questionQueryVo.getType()), Question::getType, questionQueryVo.getType());
        wrapper.like(!ObjectUtils.isEmpty(questionQueryVo.getKeyword()), Question::getTitle, questionQueryVo.getKeyword());
        wrapper.orderByDesc(Question::getCreateTime);
        page(questionPage, wrapper);
        // 简单判断 没有满足条件的题目信息，后续没有必要进行了
        if(ObjectUtils.isEmpty(questionPage.getRecords())){
            log.info("没有满足条件的题目信息");
            return ;
        }
        // 2. 查询题目对应的所有的选项和所有的答案(mybatis-plus)
        fullQuestionChoiceAndAnswer(questionPage.getRecords());
    }

    /**
     * 根据id查询题目信息
     *  题目 + 答案 + 选项-因为是多表查询
     *  方案一：嵌套结果 连表查询 + result（可以使用，没有分页）
     *  方案二：嵌套查询 分布查询 （可以，没有必要 1+n）
     *  方案三：查询+java代码赋值
     * @param id
     * @return
     */
    @Override
    public Question queryQuestionById(Long id) {
        // 1.查询题目详情
        Question question = this.getById(id);
        // 进行判断，以防在你查询的时候被别人删除了
        if(ObjectUtils.isEmpty(question)){
//            log.info("题目{}不存在，请检查id是否正确",id);
            throw new RuntimeException("题目id 为s%的题目不存在，请检查id是否正确".formatted( id));
        }
        // 2.查询题目对应的答案
        QuestionAnswer questionAnswer = questionAnswerMapper.selectOne(new LambdaQueryWrapper<QuestionAnswer>().eq(QuestionAnswer::getQuestionId, id));
        // 题目答案赋值
        question.setAnswer(questionAnswer);
        // 3.查询题目对应的选项 但是选择题才有选项，要判断
        if("CHOICE".equals(question.getType())){
            List<QuestionChoice> questionChoiceList = questionChoiceMapper.selectList(new LambdaQueryWrapper<QuestionChoice>().eq(QuestionChoice::getQuestionId, id));
            // 题目选项赋值
            question.setChoices(questionChoiceList);
        }
        // 4.题目详情赋值
        // 5.进行redis的数据缓存Zset
        new Thread(() -> {
            incrementQuestionScore(question.getId());
        }).start();

        return question;
    }

    /**
     * 创建题目
     * @param question
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createQuestion(Question question) {
        // 1. 先判断不能重复 同一个type类型下(选择 简答 判断) title不能重复
        LambdaQueryWrapper<Question> wrapper = new LambdaQueryWrapper<>();
        // 在这个type下，查看是否有相同title的题目，有的话使用count数量就会大于0
        wrapper.eq(Question::getType, question.getType());
        wrapper.eq(Question::getTitle, question.getTitle());
        long count = count(wrapper);
        if(count > 0){
            throw new RuntimeException("当前%s类型下，已存在%s题目，请勿重复添加".formatted(question.getType(), question.getTitle()));
        }
        // 2. 保存题目信息(先保存题目，保存了题目你才有题目的id，才可以进行后续的答案和选项保存)
        save(question); // 会自动主键回显
        // 3. 判断是不是选择题， 是的话就根据选项的正确给答案赋值 同时将选项插入到选项表
        QuestionAnswer answer = question.getAnswer(); // 获取答案对象 如果是选择题 答案属性为 “”
        if ("CHOICE".equals(question.getType())){
            List<QuestionChoice> questionChoices = question.getChoices();
            StringBuilder sb = new StringBuilder(); // 拼接正确答案 A，B，C，D
            for (int i = 0; i < questionChoices.size(); i++) {
                QuestionChoice choice = questionChoices.get(i);
                choice.setSort( i);
                choice.setQuestionId(question.getId());
                // 保存选项「循环 + 数据库保存」
                questionChoiceMapper.insert(choice);

                if(choice.getIsCorrect()){
                    if(sb.length() > 0){
                        sb.append( ",");
                    }
                    sb.append((char)('A'+i));
                }
            }
            // 通过选项给答案赋值
            answer.setAnswer(sb.toString());
        }

        // 4. 完成答案数据的插入
        answer.setQuestionId(question.getId());
        questionAnswerMapper.insert(answer);
    }

    /**
     * 修改题目
     * @param id 题目ID
     * @param question 题目信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateQuestion(Long id, Question question) {
        // 1. 判断,不同题目id的title不能重复
        LambdaQueryWrapper<Question> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Question::getTitle,question.getTitle());
        queryWrapper.ne(Question::getId,id);
        long count = count(queryWrapper);
        if(count > 0){
            throw new RuntimeException("当前%s类型下，已存在%s题目，请勿重复添加/修改".formatted(question.getType(), question.getTitle()));
        }
        // 2. 进行题目信息更新
        updateById(question);
        // 3. 获取答案对象
        QuestionAnswer answer = question.getAnswer();
        // 4. 判断是选择题 「1. 删除原有的选项 2. 添加新的选项 3. 拼接正确的答案 4. 给答案对象答案赋值」
        if("CHOICE".equals(question.getType())){
            List<QuestionChoice> questionChoiceList = question.getChoices();
            // 删除原有的选项
            questionChoiceMapper.delete(new LambdaQueryWrapper<QuestionChoice>().eq(QuestionChoice::getQuestionId,id));
            // 接收新的答案
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < questionChoiceList.size(); i++) {
                QuestionChoice choice = questionChoiceList.get(i);
                choice.setId(null);
                choice.setCreateTime( null);
                choice.setUpdateTime( null);
                choice.setSort(i);
                choice.setQuestionId(id);
                questionChoiceMapper.insert(choice);
                if(choice.getIsCorrect()){
                    if(sb.length() > 0){
                        sb.append( ",");
                    }
                    sb.append((char)('A'+i));
                }
            }
            answer.setAnswer(sb.toString());
        }
        // 5. 进行答案对的更新
        questionAnswerMapper.updateById(answer);
    }

    /**
     * 删除题目
     * @param id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeQuestionById(Long id) {
        // 1. 检查是否有关联的试卷题目，有删除失败
        LambdaQueryWrapper<PaperQuestion> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(PaperQuestion::getQuestionId,id);
        long count = paperQuestionMapper.selectCount(wrapper);
        if (count > 0){
            throw new RuntimeException("id = %s 的题目有关联的试卷，请先删除关联的试卷".formatted(id));
        }
        // 2. 删除题目本身
        removeById( id);
        // 3. 删除关联的子数据.选项和答案
        questionChoiceMapper.delete(new LambdaQueryWrapper<QuestionChoice>().eq(QuestionChoice::getQuestionId,id));
        questionAnswerMapper.delete(new LambdaQueryWrapper<QuestionAnswer>().eq(QuestionAnswer::getQuestionId,id));
    }

    /**
     * 获取热门题目
     * @param size
     * @return
     */
    /**
     * 查询指定数量的热门题目
     * @param size 默认：6
     * @return
     */
    @Override
    public List<Question> getPopularQuestions(Integer size) {
        //1. 定义热门题目集合（总集合）
        List<Question> popularQuestions = new ArrayList<>();

        //2. 去zset中获取热门题目，并且添加到总集合中
        // 获取题目排行，需要获取id和分数！ 分数用于后续的排序处理！
        Set<ZSetOperations.TypedTuple<Object>> tupleSet = redisUtils.zReverseRangeWithScores(CacheConstants.POPULAR_QUESTIONS_KEY, 0, size - 1);
        //定义接收id的集合
        List<Long> idsSet = new ArrayList<>();
        if (tupleSet != null && tupleSet.size() > 0) {
            //根据排行榜的积分，倒序进行Id查询！
            List<Long> idsList = tupleSet.stream().sorted((o1, o2) -> Integer.compare(o2.getScore().intValue(), o1.getScore().intValue()))
                    .map(o -> Long.valueOf(o.getValue().toString())).collect(Collectors.toList());
            //复制，用于后面补充！！
            idsSet.addAll(idsList);
            log.debug("从redis获取热门题目的id集合，且保证顺序：{}",idsList);

            for (Long id : idsList) {
                Question question = getById(id);
                if (question != null){
                    //防止redis有缓存，但是数据库中没有！ 后续优化，删除题目，应该删除热题榜单中对应的value
                    popularQuestions.add(question);
                }
            }
            log.debug("去redis查询的热门题目，题目数：{},题目内容为：{}",popularQuestions.size(),popularQuestions);
        }

        //3. 检查是否已经满足size
        int diff = size - popularQuestions.size();
        if(diff > 0){
            //4. 不满足，题目表中 非热门题目 时间倒序 limit 差数量
            LambdaQueryWrapper<Question> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.notIn(Question::getId,idsSet);
            lambdaQueryWrapper.orderByDesc(Question::getCreateTime);
            //limit diff;
            lambdaQueryWrapper.last("limit " + diff);
            List<Question> questionDiffList = list(lambdaQueryWrapper);
            log.debug("去question表中补充热门题目，题目数：{},题目内容为：{}",questionDiffList.size(),questionDiffList);
            if (questionDiffList != null && questionDiffList.size() > 0) {
                // 5. 补充也添加到总集合中
                popularQuestions.addAll(questionDiffList);
            }
        }
        //6. 总集合一起进行答案和选项填充
        fullQuestionChoiceAndAnswer(popularQuestions);
        //7. 返回即可
        return popularQuestions;
    }
    /*@Override
    public List<Question> getPopularQuestions(Integer size) {
        // 定义一个集合存储热门题目
        List<Question> popularQuestions = new ArrayList<>();
        // 1.查询redis中缓存的题目id（按照顺序访问倒序查询）
        Set<Object> popularIds = redisUtils.zReverseRange(CacheConstants.POPULAR_QUESTIONS_KEY, 0, size - 1);
        // 2. 根据查询的id查询对应的热门题目集合
        if(!ObjectUtils.isEmpty(popularIds)){
            // 转换为Long集合
            List<Long> longList = popularIds.stream().map(id -> Long.valueOf(id.toString())).collect(Collectors.toList());
            // 处理热门题目 [可能热门题目对，但是热门题目的顺序有问题]
            // List<Question> questionList = listByIds(longList);
            for (Long id : longList) {
                Question question = getById(id);
                // 校验： ID有 但是题目已经被删除！ redis 的数据和数据库数据不同步问题
                if(question != null){
                    popularQuestions.add(question);
                }
            }
        }

        // 检查题目集合数量是否满足size条
        int diff = size - popularQuestions.size();
        // 不满足，我们需要自己补充 题目表查询 -> 查询最新的题目
        if(diff > 0 ){
            // 从数据库查询最新的题目 diff个
            LambdaQueryWrapper<Question> wrapper = new LambdaQueryWrapper<>();
            wrapper.orderByDesc(Question::getCreateTime); // 根据题目的创建时间进行倒序查询，并且不能在已有的查询的集合中
            // 已有的id进行过滤
            List<Long> exisQuestionId = popularQuestions.stream().map(Question::getId).collect(Collectors.toList());
            wrapper.notIn(!ObjectUtils.isEmpty(exisQuestionId),Question::getId,exisQuestionId);
            // 切割指定的diff条
            wrapper.last("limit "+diff);
            // 获取最新的题目
            List<Question> newQuestions = list(wrapper);
            // 添加到热门题目集合中
            popularQuestions.addAll(newQuestions);
        }
        // 给题目进行选项和答案赋值即可
        fullQuestionChoiceAndAnswer(popularQuestions);

        return popularQuestions;
    }*/

    /**
     * 添加题目的分值,在排行榜中
     * @param questionId
     */
    private void  incrementQuestionScore(Long questionId){
        Double score = redisUtils.zIncrementScore(CacheConstants.POPULAR_QUESTIONS_KEY, questionId, 1);
        log.debug("完成id：{}题目的热榜分数累计，累计后的分数为：{}",questionId,score);
    }

    /**
     * 填充题目的选项和答案
     * @param questionList
     */
    private void fullQuestionChoiceAndAnswer(List<Question> questionList) {
        // 通过stream流拿到所有的题目id
        List<Long> questionIds = questionList.stream().map(Question::getId).collect(Collectors.toList());
        // 获取题目对应的选项列表
        LambdaQueryWrapper<QuestionChoice> choiceLambdaQueryWrapper = new LambdaQueryWrapper<>();
        choiceLambdaQueryWrapper.in(QuestionChoice::getQuestionId, questionIds);
        List<QuestionChoice> questionChoices = questionChoiceMapper.selectList(choiceLambdaQueryWrapper);

        // 获取题目对应的答案
        LambdaQueryWrapper<QuestionAnswer> answerLambdaQueryWrapper = new LambdaQueryWrapper<>();
        answerLambdaQueryWrapper.in(QuestionAnswer::getQuestionId, questionIds);
        List<QuestionAnswer> questionAnswers = questionAnswerMapper.selectList(answerLambdaQueryWrapper);


        // 3. 题目的选项和档案集合专程map格式 map(key => 题目id ， 题目对应的选项集合 ｜ 题目对应的答案对象)
        Map<Long, QuestionAnswer> questionAnswerMap = questionAnswers.stream().collect(Collectors.toMap(QuestionAnswer::getQuestionId, a -> a));
        // 选项转成Map
        Map<Long, List<QuestionChoice>> questionChoiceMap = questionChoices.stream().collect(Collectors.groupingBy(QuestionChoice::getQuestionId));
        // 4. 循环题目列表，进行题目的选项和方案赋值工作
        questionList.forEach(question -> {
            // 给题目的答案赋值
            question.setAnswer(questionAnswerMap.get(question.getId()));
            // 给答案的选项赋值 只有选择题有选项，选择体的Type =CHOICE
            if("CHOICE".equals(question.getType())){
                // 只要是选项的操作，一定要考虑排序的问题 sort
                List<QuestionChoice> qc = questionChoiceMap.get(question.getId());
                if(!ObjectUtils.isEmpty(qc)){
                    // 字段排序,从小到达正序  .reversed() 是倒序排序
                    qc.sort(Comparator.comparing(QuestionChoice::getSort));
                    question.setChoices(qc);
                }
            }
        });
    }
}